package org.cos.generation.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.cos.generation.epda.Method;
import org.cos.generation.util.info.ClassInfo;
import org.cos.generation.util.info.ClassTable;
import org.cos.generation.util.predict.EqualPredictSet;
import org.cos.generation.util.predict.PredictSet;
import org.cos.generation.util.predict.UnionPredictSet;
import org.cos.generation.util.symbol.SymbolList;
import org.cos.generation.util.symbol.SymbolTable;
import org.cos.generation.util.symbol.SymbolElem.SymbolKind;
import org.eclipse.jdt.core.dom.*;

/**
 * 
 * @author ZXD
 *
 */

public class DataFlowAnalyzer implements DataFlowGetter, DataFlowSetter {

	/** Store the symbol table. */
	private SymbolTable symbolTable;

	private ClassTable classTable;

	private Stack<SymbolList> symbolListStack;

	/** Mark whether entered the next block. */
	private boolean preEnterFlag = false;
   
	/** Store preEnterFlag into stack for use when exit block. */
	private Stack<Boolean> preEnterFlagStack;
	
	private String currentClassName;

	public DataFlowAnalyzer(SymbolTable symbolTable, ClassTable classTable) {
		this.symbolTable = symbolTable;
		this.classTable = classTable;
		symbolListStack = new Stack<SymbolList>();
		preEnterFlagStack = new Stack<Boolean>();
	}

	public void enterClass(TypeDeclaration node) {
		ITypeBinding typeBinding = node.resolveBinding();
		if (typeBinding != null) {
			currentClassName = typeBinding.getQualifiedName();
		}
		enterScope();
	}

	public void exitClass(TypeDeclaration node) {
		exitScope();
	}

	public void enterMethod(MethodDeclaration node) {
		// Insert new symbol.
		// Get parameters' predict set.
		ClassInfo classInfo = classTable.get(currentClassName);
		Method method = Method.valueOf(node.resolveBinding());
		List<UnionPredictSet> paraPossList = classInfo.
				getMethodInfo(method).getParaPredictSet();
		// Initialize parameters.
		int i = 0;
		for (Object o : node.parameters()) {
			SingleVariableDeclaration para = (SingleVariableDeclaration) o;
			String symName = para.getName().toString();
			PredictSet possSet = paraPossList.get(i++);
			newSymbol(SymbolKind.valueOf(para.getType()), 
					symName, para.getInitializer());
			symbolTable.putPoss(symName, possSet);
		}
		// Set pre-enter flag.
		preEnterFlag = true;
	    enterScope();
	}

	public void exitMethod(MethodDeclaration node) {
		exitScope();
	}

	public void enterBlock(Block node) {
		if (!preEnterFlag) {
			enterScope();
		} else {
			preEnterFlag = false;
		}
	}

	public void exitBlock(Block node) {
		if (!preEnterFlagStack.peek()) {
			exitScope();
		}
	}

	public void enterIf(IfStatement node) {
		// 复制当前符号表中有效的预测集合，并压栈
		symbolListStack.push(symbolTable.cloneCurrent());
	}

	public void beforeElse(IfStatement node) {
		// 复制当前符号表中有效的预测集合;
		SymbolList symbolListTmp = symbolTable.cloneCurrent();
		// 从分支符号栈顶取出分支起始处的预测集合
		// 并用这些预测集合修改将当前符号表内容
		symbolTable.modify(symbolListStack.pop());
		// 将刚才复制的预测集合压入分支符号栈中
		symbolListStack.push(symbolListTmp);
	}

	public void exitIf(IfStatement node) {
		// 将当前符号表中有效的预测集合与分支符号栈中存储的预测集合进行合并
		symbolTable.union(symbolListStack.pop());
	}

	public void enterFor(ForStatement node) {
		preEnterFlag = true;
		enterScope();
	}

	public void exitFor(ForStatement node) {
		exitScope();
	}

	public void enterWhile(WhileStatement node) {
	}

	public void exitWhile(WhileStatement node) {
	}

	public void enterDo(DoStatement node) {
	}

	public void exitDo(DoStatement node) {
	}

	public void enterDeclaration(SingleVariableDeclaration node) {
		// Get symbol type.
		newSymbol(SymbolKind.valueOf(node.getType()), 
				node.getName().toString(), node.getInitializer());      
	}
   
	public void enterDeclaration(VariableDeclarationExpression node) {
		// Get symbol type.
		SymbolKind typeKind = SymbolKind.valueOf(node.getType());
		for (Object o : node.fragments()) {
       		VariableDeclarationFragment fragment = (VariableDeclarationFragment) o;
			newSymbol(typeKind, fragment.getName().toString(), 
					fragment.getInitializer()); 
		}       
	}
   
	public void enterDeclaration(VariableDeclarationStatement node) {
		// Get symbol type.
		SymbolKind typeKind = SymbolKind.valueOf(node.getType());
		for (Object o : node.fragments()) {
       		VariableDeclarationFragment fragment = (VariableDeclarationFragment) o;
			newSymbol(typeKind, fragment.getName().toString(), 
					fragment.getInitializer()); 
       	}       
	}

	public void enterDeclaration(FieldDeclaration node) {
		SymbolKind typeKind = SymbolKind.valueOf(node.getType());
       	for (Object o : node.fragments()) {
       		VariableDeclarationFragment fragment = 
       				(VariableDeclarationFragment) o;
       		newSymbol(typeKind, fragment.getName().toString(), 
       				fragment.getInitializer()); 
       	}   
	}

	public void enterAssignment(Assignment node) {
		String symName = node.getLeftHandSide().toString();
		// 获得右侧表达式的预测集合
		Expression exp = node.getRightHandSide();
		// 用该预测集合替换左侧符号在符号表中的预测集合
   		symbolTable.putPoss(symName, getExpPredict(exp));
	}

	@SuppressWarnings("unchecked")
	public void enterInvocation(MethodInvocation node) {
		// Get the full name of the method.
		IMethodBinding methodBinding = node.resolveMethodBinding();
		if (methodBinding != null) {
			handleInvocation(methodBinding, node.arguments());
		}
	}
	
	private void handleInvocation(IMethodBinding methodBinding, 
			List<Expression> paraList) {
		String qualifiedTypeName = null;
		if (methodBinding != null) {
			ITypeBinding typeBinding = methodBinding.getDeclaringClass();
			if (typeBinding != null) {
				qualifiedTypeName = typeBinding.getQualifiedName();
			}
		}
		// Get class info.
		ClassInfo classInfo = classTable.get(qualifiedTypeName);
		// When it is a method of custom class.
		if (classInfo != null) {
			// Get the parameters' possible set.
			List<PredictSet> paraPredictList = getParaPredict(paraList);
			Method method = Method.valueOf(methodBinding);
			classInfo.getMethodInfo(method).
					addParaPredict(paraPredictList);
		}
	}
	
	/**
	 * Return the possible set of the given expression.
	 * @param exp Expression.
	 * @return Possible set.
	 */
   	@SuppressWarnings("unchecked")
	public PredictSet getExpPredict(Expression node) {
   		if (node != null) {
		   	switch (node.getNodeType()) {
		   		// Handle the identifier.
		   		case ASTNode.SIMPLE_NAME :
		   			return symbolTable.getSymbolPoss(node.toString());
		   		// Handle the type-cast.
		   		case ASTNode.CAST_EXPRESSION :
		   			return getExpPredict(((CastExpression)node).getExpression());
		   		// Handle the new-instance.
		   		case ASTNode.NUMBER_LITERAL :
		   			return new EqualPredictSet(node.resolveConstantExpressionValue());
		   		case ASTNode.STRING_LITERAL :
		   			return new EqualPredictSet(node.resolveConstantExpressionValue());
		   		case ASTNode.CLASS_INSTANCE_CREATION :
		   			ClassInstanceCreation instanceNode = (ClassInstanceCreation)node;
		   			handleInvocation(instanceNode.resolveConstructorBinding(),
		   					instanceNode.arguments());
		   			Type type = instanceNode.getType();
		   			ITypeBinding typeBinding = type.resolveBinding();
		   			String typeName;
		   			if (typeBinding != null) {
		   				typeName = typeBinding.getQualifiedName();
		   			} else {
		   				typeName = type.toString();
		   			}
		   			return new EqualPredictSet(typeName);
		   		// Could not handle.
		   		default :
		   			return null;
		   	}
   		}
   		return null;
   	}
   	
   	public List<PredictSet> getParaPredict(List<Expression> paraList) {
   		int paraNum = paraList.size();
		List<PredictSet> paraPredictList = new ArrayList<PredictSet>(paraNum);
		for (Expression exp : paraList) {
			PredictSet predict = getExpPredict(exp);
			paraPredictList.add(predict);
		}
		return paraPredictList;
   	}
    
	private void enterScope() {
		preEnterFlagStack.push(preEnterFlag);
		symbolTable.enterScope();
	}
   
	private void exitScope() {
		preEnterFlagStack.pop();
       	symbolTable.exitScope();
   	}

	/**
	 * Handle a symbol declaration.
	 * @param typeKind Kind of the symbol's type.
	 * @param symName Name of the symbol.
	 * @param init Initializer of the symbol.
	 */
	private void newSymbol(SymbolKind typeKind, String symName, Expression init) {
		// 将符号插入符号表
		symbolTable.insertSymbol(symName, typeKind);
       	// 初始化该符号的预测集
        symbolTable.putPoss(symName, getExpPredict(init));
	}

   
}
